using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Data;
using System.Text;
using System.Windows.Forms;
using UmlDesigner.Controller;
using System.Diagnostics;
using UmlDesigner.Model.WorkUnits;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
using UmlDesigner.Model;

namespace UmlDesigner.Gui
{
    internal partial class UmlControl : UserControl
    {
        public UmlControl()
        {
            InitializeComponent();

            SetStyle(ControlStyles.AllPaintingInWmPaint, true);
            SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
        }

        internal Document currentDocument;

        private void OnPaint(object sender, PaintEventArgs e)
        {
            if (Properties.Settings.Default.UseAntiAliasing)
            {
                // Links need anti-aliasing to look semi-decent
                e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
                e.Graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
            }

            PaintCurrentDocument(e.Graphics);
        }

        public void PaintCurrentDocument(Graphics g)
        {
            LinearGradientBrush backgroundBrush = new LinearGradientBrush(ClientRectangle, Color.LemonChiffon, Color.White, 45.0f);

            g.FillRectangle(backgroundBrush, ClientRectangle);

            g.ScaleTransform(scalingFactor, scalingFactor);

            if (drawingArrow)
            {
                currentLink.PaintItem(g);
            }

            if (currentDocument != null)
            {
                foreach (ItemWidget widget in currentDocument.items)
                {
                    widget.PaintItem(g);
                    if (selection.Contains(widget))
                    {
                        if (widget == focused)
                        {
                            widget.HandleFocusedOutline(g);
                        }
                        else
                        {
                            widget.PaintSelectionOutline(g);
                        }
                        
                    }
                }
            }

            if (selecting && selectionRect.Height != 0 && selectionRect.Width != 0)
            {
                PaintSelection(g);
            }

            backgroundBrush.Dispose();
        }


        private void PaintSelection(Graphics e)
        {
            Color darkBlue = Color.FromArgb(150, 0, 50, 160);
            Color lightBlue = Color.FromArgb(150, 200, 200, 255);
            LinearGradientBrush selectionBrush = new LinearGradientBrush(selectionRect, darkBlue, lightBlue, 45.0f);

            e.FillRectangle(selectionBrush, selectionRect);
            e.DrawRectangle(new Pen(darkBlue), selectionRect);
        }

        private void OnSizeChanged(object sender, EventArgs e)
        {
            Invalidate(ClientRectangle);
        }

        private bool selecting = false;
        private Point anchor;
        private Rectangle selectionRect = new Rectangle();

        private bool dragging = false;

        private bool drawingArrow = false;
        private Point currentArrowPoint = new Point();
        private TemporaryLinkWidget currentLink = null;

        internal List<ItemWidget> selection = new List<ItemWidget>();
        private ItemWidget focused;

        internal ItemWidget GetItemAtPoint(Point p)
        {
            foreach (ItemWidget c in currentDocument.items)
            {
                if (c.IsPointWithin(p))
                {
                    return c;
                }
            }
            return null;
        }

        private Point MouseEventToPoint(MouseEventArgs e)
        {
            return new Point((int)(((float)e.X) / scalingFactor), (int)(((float)e.Y) / scalingFactor));
        }

        private void OnMouseDown(object sender, MouseEventArgs e)
        {
            anchor = MouseEventToPoint(e);

            if (mainForm.LinkState)
            {
                ItemWidget c = GetItemAtPoint(MouseEventToPoint(e));
                if (c != null)
                {
                    drawingArrow = true;
                    currentArrowPoint = MouseEventToPoint(e);
                    currentLink = new TemporaryLinkWidget(c);
                    return;
                }

                mainForm.LinkState = false;
            }

            BeginSelectionMode(e);
        }

        private void BeginSelectionMode(MouseEventArgs e)
        {
            ItemWidget clickedItem = null;

            foreach (ItemWidget c in currentDocument.items)
            {
                if (c.IsPointWithin(MouseEventToPoint(e)))
                {
                    if (selection.Contains(c) || (clickedItem == null))
                    {
                        // prefer selected items over non-selected
                        clickedItem = c;
                    }
                }
            }

            if (clickedItem != null)
            {
                if (focused != null) focused.OnKiilFocus();
                clickedItem.OnSetFocus();
                focused = clickedItem;
                if (!selection.Contains(clickedItem))
                {
                    ClearSelectionIfShiftUp();
                    selection.Add(clickedItem);
                }

                moveItems = new MoveItemsWorkUnit(selection, anchor);

                dragging = true;
                return;
            }

            ClearSelectionIfShiftUp();

            selecting = true;
            selectionRect.Location = anchor;
            selectionRect.Size = new Size(0, 0);
        }

        MoveItemsWorkUnit moveItems = null;

        private void ClearSelectionIfShiftUp()
        {
            bool shiftDown = (Control.ModifierKeys & Keys.Shift) == Keys.Shift;
            if (!shiftDown)
            {
                selection.Clear();
            }
        }

        private void OnMouseUp(object sender, MouseEventArgs e)
        {
            if (drawingArrow)
            {
                if (currentLink.LinkComplete())
                {
                    LinkWidget link = currentLink.CreateRealLinkWidget();
                    mainForm.AddWorkUnit(new NewLinkWorkItem(link));
                }

                currentLink.GetSource().FreeSlot(currentLink);

                currentDocument.isModified = true;
                drawingArrow = false;
                currentLink = null;
            }
            if (dragging)
            {
                moveItems.Position = anchor;
                mainForm.AddWorkUnit(moveItems);
                moveItems = null;

                currentDocument.isModified = true;
            }
            dragging = false;
            selecting = false;
            Invalidate();
        }

        private bool IsConnectionValid(ItemWidget src, ItemWidget dest)
        {
            if (src.IsNote() && dest.IsNote())
                return false;

            if ((src.IsLink() && !dest.IsNote()) || (dest.IsLink() && !src.IsNote()))
                return false;

            return true;
        }

        private void OnMouseMove(object sender, MouseEventArgs e)
        {
            if (drawingArrow)
            {
                currentArrowPoint = MouseEventToPoint(e);
                currentLink.SetDestPoint(MouseEventToPoint(e));
                foreach (ItemWidget c in currentDocument.items)
                {
                    if (c.IsPointWithin(MouseEventToPoint(e)))
                    {
                        // Validate the link creation here...
                        if (IsConnectionValid(currentLink.GetSource(), c))
                        {
                            currentLink.SetDestClass(c);
                        }
                    }
                }
            }
            else if (dragging)
            {
                int dx = (MouseEventToPoint(e).X - anchor.X);
                int dy = (MouseEventToPoint(e).Y - anchor.Y);
                MoveSelection(dx, dy);
                anchor = MouseEventToPoint(e);
                mainForm.RecalculateUmlControlSize();
            }
            else if (selecting)
            {
                Point tl = new Point(Math.Min(MouseEventToPoint(e).X, anchor.X), Math.Min(MouseEventToPoint(e).Y, anchor.Y));
                Point br = new Point(Math.Max(MouseEventToPoint(e).X, anchor.X), Math.Max(MouseEventToPoint(e).Y, anchor.Y));

                selectionRect.Location = tl;
                selectionRect.Size = new Size(br.X - tl.X, br.Y - tl.Y);

                // Recalculate the selection list.
                ClearSelectionIfShiftUp();

                foreach (ItemWidget c in currentDocument.items)
                {
                    if (c.ItemWithinArea(selectionRect))
                    {
                        selection.Add(c);
                    }
                }
            }
            Invalidate();
        }

        private void MoveSelection(int dx, int dy)
        {
            foreach (ItemWidget item in selection)
            {
                item.SetClassPosition(new Point(item.Position.X + dx, item.Position.Y + dy));
                if (dx % 2 == 0 || dy % 2 == 0)
                {
                    item.RecalculateAllSlots();
                }
            }
        }

        private void OnMouseDoubleClick(object sender, MouseEventArgs e)
        {
            foreach (ItemWidget c in currentDocument.items)
            {
                if (c != null)
                {
                    if (c.IsPointWithin(MouseEventToPoint(e)))
                    {
                        c.HandleDoubleClicked(this);
                        return;
                    }
                }
            }
        }

        internal void AlignSelectionToTop()
        {
            if (selection.Count == 0)
            {
                return;
            }

            int topPos = selection[0].Position.Y;

            CompositeWorkUnit unit = new CompositeWorkUnit();
            foreach (ItemWidget item in selection)
            {
                unit.AddItem(new MoveWorkUnit(item, item.Position.X, topPos));
            }
            mainForm.AddWorkUnit(unit);

            Invalidate();
        }

        internal void AlignSelectionToLeft()
        {
            if (selection.Count == 0)
            {
                return;
            }

            int leftPos = selection[0].Position.X;

            CompositeWorkUnit unit = new CompositeWorkUnit();
            foreach (ItemWidget item in selection)
            {
                unit.AddItem(new MoveWorkUnit(item, leftPos, item.Position.Y));
            }
            mainForm.AddWorkUnit(unit);

            Invalidate();
        }

        internal void AlignSelectionToBottom()
        {
            if (selection.Count == 0)
            {
                return;
            }

            int bottomPos = selection[0].Position.Y + selection[0].ClassRect.Height;

            CompositeWorkUnit unit = new CompositeWorkUnit();
            foreach (ItemWidget item in selection)
            {
                unit.AddItem(new MoveWorkUnit(item, item.Position.X, bottomPos - item.ClassRect.Height));
            }
            mainForm.AddWorkUnit(unit);

            Invalidate();
        }

        internal void AlignSelectionToRight()
        {
            if (selection.Count == 0)
            {
                return;
            }

            int rightPos = selection[0].Position.X + selection[0].ClassRect.Width;
            CompositeWorkUnit unit = new CompositeWorkUnit();

            foreach (ItemWidget item in selection)
            {
                unit.AddItem(new MoveWorkUnit(item, rightPos - item.ClassRect.Width, item.Position.Y));
            }
            mainForm.AddWorkUnit(unit);
            Invalidate();
        }

        MainForm mainForm;

        internal MainForm MainForm
        {
            get
            {
                return mainForm;
            }
            set
            {
                mainForm = value;
            }
        }

        internal Size GetDocumentSize()
        {
            int maxX = 0, maxY = 0;

            foreach (ItemWidget c in currentDocument.items)
            {
                maxX = Math.Max(maxX, c.ClassRect.Right);
                maxY = Math.Max(maxY, c.ClassRect.Bottom);
            }

            return new Size(maxX + 50, maxY + 50);
        }

        //private string fileName = null;

        //internal void LoadDocument(string filename)
        //{
        //    fileName = filename;
        //    FileStream stream = new FileStream(filename, FileMode.Open);
        //    LoadFromStream(stream);
        //}

        internal void MergeLoadFromBytes(byte[] p)
        {
            MemoryStream stream = new MemoryStream(p);
            Document insertedDoc = Project.Instance.MergeLoadDocument(currentDocument, stream);
            stream.Close();
            selection.Clear();
            selection.AddRange(insertedDoc.items);
            UpdateControlSize();
            Invalidate();
        }

        //private void LoadFromStream(Stream stream)
        //{
        //    DocumentSet.Instance.DeleteDocument(currentDocument);
        //    try
        //    {
        //        currentDocument = DocumentSet.Instance.LoadDocument(stream);
        //    }
        //    catch (Exception e)
        //    {
        //        MessageBox.Show("An error occurred loading the file: " + e.Message, "Error loading file", MessageBoxButtons.OK, MessageBoxIcon.Error);
        //    }
        //    stream.Close();
        //    UpdateControlSize();
        //    Invalidate();
        //}

        internal void CutSelectionToClipboard()
        {
            CopySelectionToClipboard();
            DeleteSelection();
        }

        internal void CopySelectionToClipboard()
        {
            UmlClipboardData.CopyToClipboard(selection);
        }

        internal void DistributeSpaceVertically()
        {
            SortedList<int, ItemWidget> sortedItems = new SortedList<int, ItemWidget>();

            if (selection.Count < 2)
            {
                return;
            }

            int top = 1000000, bottom = 0;
            int totalHeight = 0;

            foreach (ItemWidget item in selection)
            {
                if (!item.IsLink())
                {
                    if (item.ClassRect.Top < top)
                    {
                        top = item.ClassRect.Top;
                    }

                    if (item.ClassRect.Bottom > bottom)
                    {
                        bottom = item.ClassRect.Bottom;
                    }

                    totalHeight += item.ClassRect.Height;
                    int yInsert = item.GetClassPosition().Y;
                    while (sortedItems.ContainsKey(yInsert))
                    {
                        yInsert++;
                    }
                    sortedItems.Add(yInsert, item);
                }
            }

            int spacePerItem = ((bottom - top) - totalHeight) / (sortedItems.Count - 1);
            int currentOffset = top;
            if (spacePerItem > 0)
            {
                foreach (KeyValuePair<int, ItemWidget> item in sortedItems)
                {
                    Point p = item.Value.GetClassPosition();
                    p.Y = currentOffset;
                    item.Value.SetClassPosition(p);

                    currentOffset += item.Value.ClassRect.Height + spacePerItem;
                }
            }
            Invalidate();
        }

        internal void DistributeSpaceHorizontally()
        {
            SortedList<int, ItemWidget> sortedItems = new SortedList<int, ItemWidget>();

            if (selection.Count < 2)
            {
                return;
            }

            int left = 1000000, right = 0;
            int totalWidth = 0;

            foreach (ItemWidget item in selection)
            {
                if (!item.IsLink())
                {
                    if (item.ClassRect.Left < left)
                    {
                        left = item.ClassRect.Left;
                    }

                    if (item.ClassRect.Right > right)
                    {
                        right = item.ClassRect.Right;
                    }

                    totalWidth += item.ClassRect.Width;
                    int xInsert = item.GetClassPosition().X;
                    while (sortedItems.ContainsKey(xInsert))
                    {
                        xInsert++;
                    }
                    sortedItems.Add(xInsert, item);
                }
            }

            int spacePerItem = ((right - left) - totalWidth) / (sortedItems.Count - 1);
            int currentOffset = left;
            if (spacePerItem > 0)
            {
                foreach (KeyValuePair<int, ItemWidget> item in sortedItems)
                {
                    Point p = item.Value.GetClassPosition();
                    p.X = currentOffset;
                    item.Value.SetClassPosition(p);

                    currentOffset += item.Value.ClassRect.Width + spacePerItem;
                }
            }
            Invalidate();
        }

        internal void DeleteSelection()
        {
            CompositeWorkUnit deleteSelection = new CompositeWorkUnit();

            foreach (ItemWidget item in selection)
            {
                deleteSelection.AddItem(new DeleteItemWorkUnit(item, currentDocument));
            }

            mainForm.AddWorkUnit(deleteSelection);

            selection.Clear();
            Invalidate();
        }

        internal void SetScalingFactor(float amount)
        {
            scalingFactor = amount;
            UpdateControlSize();
            Invalidate();
        }

        internal float GetScalingFactor()
        {
            return scalingFactor;
        }

        internal void UpdateControlSize()
        {
            int newWidth = (int)((float)currentDocument.DocumentSize.Width * scalingFactor);
            int newHeight = (int)((float)currentDocument.DocumentSize.Height * scalingFactor);
            Size = new Size(newWidth, newHeight);
        }

        private float scalingFactor = 1.0f;

        internal void ResizeDocument(Size size)
        {
            currentDocument.DocumentSize = size;
            UpdateControlSize();
            Invalidate();
        }

        internal Bitmap GetDocumentAsImage()
        {
            if (currentDocument.items.Count == 0)
            {
                return null;
            }

            Bitmap bmp;
            Rectangle minImageSize = GetMinImageSize();
            bmp = new Bitmap(minImageSize.Width, minImageSize.Height);
            Bitmap tmpBmp = new Bitmap(Width, Height);
            DrawToBitmap(tmpBmp, ClientRectangle);
            Graphics g = Graphics.FromImage(bmp);

            g.DrawImage(tmpBmp, new Rectangle(0, 0, minImageSize.Width, minImageSize.Height), minImageSize, GraphicsUnit.Pixel);

            string productTag = "Created by Green Bird Software UML Designer";
            Size tagSize = g.MeasureString(productTag, SystemFonts.DefaultFont).ToSize();
            g.DrawString(productTag, SystemFonts.DefaultFont, Brushes.Black, new Point(minImageSize.Width - (tagSize.Width), minImageSize.Height - (tagSize.Height + 4)));
            g.Dispose();

            return bmp;
        }

        internal Rectangle GetMinImageSize()
        {

            int l = Width, r = 0, t = Height, b = 0;
            foreach (ItemWidget w in currentDocument.items)
            {
                if (w.ClassRect.Right > r)
                {
                    r = w.ClassRect.Right;
                }
                if (w.ClassRect.Bottom > b)
                {
                    b = w.ClassRect.Bottom;
                }
                if (w.ClassRect.Top < t)
                {
                    t = w.ClassRect.Top;
                }
                if (w.ClassRect.Left < l)
                {
                    l = w.ClassRect.Left;
                }
            }

            int borderSize = 20;
            int textBorderSize = 40;

            l = Math.Max(0, l - borderSize);
            t = Math.Max(0, t - borderSize);
            r = Math.Min(Width, r + borderSize);
            b = Math.Min(Height, b + textBorderSize);

            return new Rectangle(l, t, Math.Max(r - l, 240), b - t);
        }
    }
}
